package com.spzx.product.service.impl;

import com.spzx.common.core.utils.StringUtils;
import com.spzx.product.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 锁的V1版本：本地锁
     * 问题：无法解决分布式环境下并发问题
     */
//    @Override
    public synchronized void testLockV1() {

        String value = redisTemplate.opsForValue().get("num");
        if(StringUtils.isBlank(value)){
            return;
        }
        int num = Integer.parseInt(value);
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
    }


    /**
     * 锁的V2版本：分布式锁
     * 实现方案：使用redis的setnx命令，java中的setIfAbsent方法，返回true表示获取锁成功，返回false表示获取锁失败
     * 注意事项：
     * 1、为了让锁能够成功被释放，要将释放锁的代码写在finnaly中
     * 2、为了防止服务器直接宕机，要为锁设置超时时间，防止死锁
     * 3、设置超时时间要注意确保原子性
     *
     * 问题：可能误删其他锁
     */
//    @Override
    public void testLockV2() {

        //获取锁
        //Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "lock");//setnx
        //设置超时时间(这种方式缺乏原子性)
        //redisTemplate.expire("lock", 3, TimeUnit.SECONDS);
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "lock", 3, TimeUnit.SECONDS);//setnx

        //判断是否成功持有锁
        if(flag){
            try {
                //业务逻辑
                String value = redisTemplate.opsForValue().get("num");

                //int a = 1/0;

                if(StringUtils.isBlank(value)){
                    return;
                }
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (NumberFormatException e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                redisTemplate.delete("lock"); //del
            }

        }else{
            //获取锁失败，重试
            //注意：要间隔重试，不能无限重试，否则会消耗大量CPU资源
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            testLock();
        }

    }

    /**
     * 锁的V3版本：分布式锁
     * 实现方案：使用uuid作为锁的值，防止误删、将锁的过期时间设置的稍微长一些
     * 问题：释放锁没有原子性
     */
//    @Override
    public void testLockV3() {

        //获取锁
        String uuid = UUID.randomUUID().toString();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);//setnx

        //判断是否成功持有锁
        if(flag){
            try {
                //业务逻辑
                String value = redisTemplate.opsForValue().get("num");

                //int a = 1/0;

                if(StringUtils.isBlank(value)){
                    return;
                }
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (NumberFormatException e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                if(uuid.equals(redisTemplate.opsForValue().get("lock"))){
                    //原子性问题：当jvm1中equals方法执行完，锁恰好过期，jvm2获取锁，之后jvm1删除锁，此时删除的是jvm2的锁
                    redisTemplate.delete("lock"); //del
                }
            }

        }else{
            //获取锁失败，重试
            //注意：要间隔重试，不能无限重试，否则会消耗大量CPU资源
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            testLock();
        }
    }

    /**
     * 锁的V4版本：分布式锁
     * 实现方案：使用lua脚本保证锁的释放的原子性
     */
    @Override
    public void testLock() {

        //获取锁
        String uuid = UUID.randomUUID().toString();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);//setnx

        //判断是否成功持有锁
        if(flag){
            try {
                //业务逻辑
                String value = redisTemplate.opsForValue().get("num");

                //int a = 1/0;

                if(StringUtils.isBlank(value)){
                    return;
                }
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (NumberFormatException e) {
                throw new RuntimeException(e);
            } finally {
                //lua脚本
                String script = """
                        if redis.call('get', KEYS[1]) == ARGV[1] then
                            return redis.call('del', KEYS[1])
                        else
                            return 0
                        end
                        """;

                //释放锁
                Boolean lock = redisTemplate.execute(
                        new DefaultRedisScript<Boolean>(script, Boolean.class),
                        Arrays.asList("lock"),
                        uuid);

                System.out.println("锁释放成功：" + lock);
                /*if(uuid.equals(redisTemplate.opsForValue().get("lock"))){
                    //原子性问题：当jvm1中equals方法执行完，锁恰好过期，jvm2获取锁，之后jvm1删除锁，此时删除的是jvm2的锁
                    redisTemplate.delete("lock"); //del
                }*/
            }

        }else{
            //获取锁失败，重试
            //注意：要间隔重试，不能无限重试，否则会消耗大量CPU资源
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            testLock();
        }
    }
}
